package com.myblog.demo.websocket;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.myblog.demo.common.ApplicationVariable;
import com.myblog.demo.entity.Message;
import com.myblog.demo.entity.UserInfo;
import com.myblog.demo.entity.vo.Friend;
import com.myblog.demo.manager.OnlineUserManager;
import com.myblog.demo.service.MessageService;
import com.myblog.demo.service.MessageSessionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 该类用于消息的中转
 * 继承自 TextWebSocketHandler
 */
@Service
// 引入 Spring Boot 日志对象
@Slf4j
public class MyWebSocketHandler extends TextWebSocketHandler {

    @Autowired
    private OnlineUserManager onlineUserManager;

    @Autowired
    private MessageSessionService messageSessionService;

    @Autowired
    private MessageService messageService;


    public final ObjectMapper objectMapper = new ObjectMapper();

    /*
     注册 JavaTimeModule 到 ObjectMapper 中，
     使得 Jackson 能够正确地序列化和反序列化 Java 8 的日期/时间类型，如 LocalDateTime。
     */
    {
        objectMapper.registerModule(new JavaTimeModule());
    }

    /**
     * 此方法会在 WebSocket 连接之后自动调用
     *
     * @param session WebSocketSession
     * @throws Exception 异常信息
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("[WesSock] 连接成功！");

        // 从 HttpSession 中获取当前登录用户的id
        UserInfo loginUser = (UserInfo) session.getAttributes().get(ApplicationVariable.SESSION_KEY_USERINFO);
        if (loginUser == null) {
            return;
        }

        log.info("获取到：userId = " + loginUser.getId());

        // 储存键值对
        onlineUserManager.online(loginUser.getId(), session);
    }

    /**
     * 此方法会在收到消息的时候被自动调用
     * 用于消息转发
     *
     * @param session WebSocketSession
     * @param message 消息内容
     * @throws Exception 异常信息
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("[WesSock] 收到消息：" + message.toString());
        // 1. 获取当前登录用户信息
        UserInfo loginUser = (UserInfo) session.getAttributes().get(ApplicationVariable.SESSION_KEY_USERINFO);
        if (loginUser == null) {
            log.warn("[WesSock] loginUser 对象为空，未登录用户，无法进行消息转发！");
            return;
        }

        // 2. 对请求进行解析，使用 ObjectMapper 将 JSON 格式的 message 解析成 MessageRequest 对象
        MessageRequest messageRequest = objectMapper.readValue(message.getPayload(), MessageRequest.class);
        if (messageRequest.getType().equals("message")) {
            // 进行信息转发
            transferMessage(loginUser, messageRequest);
        } else {
            log.warn("[WesSock] 请求类型错误，无法转发：" + messageRequest.getType());
        }
    }

    /**
     * 此方法用于实际消息的转发
     * 前端：需要转发给在线的用户，包括自己
     * 后端：通过服务层持久化消息到数据库
     *
     * @param fromUser 发送消息的用户
     * @param request  消息请求对象
     */
    private void transferMessage(UserInfo fromUser, MessageRequest request) throws IOException {
        // 1. 首先构造一个 WebSocket 响应对象 MessageResponse
        MessageResponse response = new MessageResponse();
        response.setFromId(fromUser.getId());
        response.setSessionId(request.getSessionId());
        response.setContent(request.getContent());
        response.setCreateTime(LocalDateTime.now());

        // 将其序列化成 JSON 格式
        String respJson = objectMapper.writeValueAsString(response);

        List<Friend> friends = new ArrayList<>();
        // 2. 根据消息请求对象中的 sessionId 决定要发送给哪个用户
        Friend myFriend = messageSessionService.getFriendBySessionId(request.getSessionId(), fromUser.getId());
        friends.add(myFriend);

        // 除了发送给对话对象之外，还需要发送给自己
        Friend myself = new Friend();
        myself.setFriendId(fromUser.getId());
        myself.setUsername(fromUser.getUsername());
        myself.setPhoto(fromUser.getPhoto());
        myself.setNickname(fromUser.getNickname());
        friends.add(myself);

        // 3. 给该对话中的所有在线用户发送响应消息
        for (Friend friend : friends) {
            // 通过 friend 的 id 获取其 WebSocketSession
            WebSocketSession webSocketSession = onlineUserManager.getSession(friend.getFriendId());
            if (webSocketSession == null) {
                log.info("[WebSocket] id = " + friend.getFriendId() + "的用户没有上线，不进行转发");
                continue;
            }
            // 通过该用户的 webSocketSession 发送响应消息
            webSocketSession.sendMessage(new TextMessage(respJson));
        }


        // 4. 数据库持久化

        // 创建 Message 对象
        Message message = new Message();
        message.setFromId(fromUser.getId());
        message.setSessionId(response.getSessionId());
        message.setContent(response.getContent());
        message.setCreateTime(response.getCreateTime());

        // 持久化
        if (messageService.addMessage(message) != 1) {
            log.warn("消息持久化失败！");
        }
    }

    /**
     * 此方法会在出现异常的时候自动调用，
     * 连接出现异常的时候直接下线当前用户
     *
     * @param session   WebSocketSession
     * @param exception 连接异常消息
     * @throws Exception 异常信息
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.warn("[WebSocket] 连接异常：" + exception.toString());
        // 1. 获取当前登录用户信息
        UserInfo loginUser = (UserInfo) session.getAttributes().get(ApplicationVariable.SESSION_KEY_USERINFO);
        if (loginUser == null) {
            log.warn("[WesSock] loginUser 对象为空，未登录用户！");
            return;
        }

        // 2. 下线该用户
        log.warn("[WebSocket] 下线用户：" + loginUser.getId());
        onlineUserManager.offline(loginUser.getId(), session);
    }

    /**
     * 此方法会在 WebSocket 连接断开后自动调用，断开连接之后，下线当前用户
     *
     * @param session WebSocketSession
     * @param status  断开的状态
     * @throws Exception 异常信息
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("[WebSocket] 断开连接！");
        // 1. 获取当前登录用户信息
        UserInfo loginUser = (UserInfo) session.getAttributes().get(ApplicationVariable.SESSION_KEY_USERINFO);
        if (loginUser == null) {
            log.warn("[WesSock] loginUser 对象为空，未登录用户！");
            return;
        }

        // 2. 下线该用户
        log.info("[WebSocket] 下线用户：" + loginUser.getId());
        onlineUserManager.offline(loginUser.getId(), session);
    }
}
